﻿/*
 *  Author: Louis A. Leskoviansky
 *  Company: Artemis Solutions Group (http://www.artemis-solutions.com)
 */


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.UI;

namespace AjaxControlFramework
{
	public class ControlCallbackStrategy : CallbackStrategy
	{
		//------// Constructors \\------------------------------------------\\
		public ControlCallbackStrategy(Control control)
			: base(control)
		{ }
		//------\\ Constructors //------------------------------------------//



		//------// Methods \\-----------------------------------------------\\
        public override Control LoadControl(CallbackStrategyArguments args)
        {
            return this.Control as Control;
        }


        public override Control LoadGlobalControl(CallbackStrategyArguments args)
        {
            // Some interesting stuff is done in this method. So we need to load the global control 
            // and have it initialize against the current Page so that it has access to the 
            // Request, Response, Session, Cache, URL paremeters and others like the Control would 
            // if it were being loaded normally as part of a normal Page Lifecycle. The standard 
            // way of loading a dynamic control is to all the "LoadControl" method of the 
            // TemplateControl class and add it to the page, or to a child control. We COULD do that 
            // here but when we add the global control to a parent it's lifecycle events will be 
            // performed automatically to "catch up" with the control in context. If this were the case 
            // then there would be no way to sneak in the logic of the StateStrategy. So, what is 
            // being done here is a general instantiation of the global control object, using a 
            // late-bound Dynamic Method delegate that is being cached in a static property, and 
            // each of the Page Lifecycle events are being invoked in their correct order (with the 
            // inclusion of the StateStrategy logic). Magnificent!

            Control globalControl = CreateControl(AjaxControlUtils.ResolveControlType(args.controlType));

            if (globalControl == null)
            {
                throw new ArgumentException("The control Type string provided does not resolve to a valid object type. Be sure to provide the fully qualified type name of the global control being invoked/updated.");
            }


            TemplateControl templateControl = globalControl as TemplateControl;
            if (templateControl != null)
            {
                if (templateControl.AppRelativeVirtualPath == null || templateControl.AppRelativeVirtualPath.Trim().Length == 0)
                {
                    throw new InvalidOperationException("IAjaxControl implementations that derive from " + typeof(TemplateControl).FullName + " must provide a valid AppRelativeVirtualPath property value.");
                }


                Type compiledTypeThatIncludesMarkup = System.Web.Compilation.BuildManager.GetCompiledType(templateControl.AppRelativeVirtualPath);
                if (compiledTypeThatIncludesMarkup != null)
                {
                    globalControl = CreateControl(compiledTypeThatIncludesMarkup);
                }
            }


            if (globalControl is IAjaxControl)
            {
                // IAjaxControl instances being loaded globally should use their own loading logic.
                globalControl = (globalControl as IAjaxControl).CallbackStrategy.LoadControl(args);
            }


            Control.StateStrategy.TargetControl = globalControl; // This ensures that the global control get loaded from and saved to viewstate.

            // Invoke the Page Lifecycle events against the global control in order.

            // With the InitRecursive method, you can pass to it a NamingContainer instance 
            // that the global control will be initialized against (all without adding the 
            // global control to the Page's Controls collection). If the Control in 
            // context (the one that received the callback) implements INamingContainer then 
            // it will be the global control's naming container. Otherwise, the current 
            // Page will be the global control's naming container.

            if (Control is INamingContainer)
            {
                InitRecursive(globalControl, Control as Control);
                SetParent(globalControl, Control as Control);
            }
            else
            {
                InitRecursive(globalControl, ParentPage);
                SetParent(globalControl, ParentPage);
            }

            // Load the ControlState and ViewState of the global Control.
            if (Control.StateStrategy != null && globalControl.EnableViewState)
            {
                Control.StateStrategy.Load();
            }

            LoadRecursive(globalControl);

            return globalControl;
        }


		protected override Dictionary<string, object> GlobalUpdate(CallbackStrategyArguments args)
		{
			// Load the control so that is may be initialized and its state restored from its AJAX context.
            Control control = LoadGlobalControl(args);

			if (!(control is IAjaxControl))
			{
				throw new System.Exception("The control being referenced by the 'Type' parameter of the GlobalUpdate method must implement the IAjaxControl interface.");
			}

            IAjaxControl ajaxControl = (IAjaxControl)control;

			
			// Create the context object to wrap the state and HTML of the IAjaxControl.
			AjaxControlContext newContext = new AjaxControlContext {
                State = args.state,
				Html = String.Empty,
				Control = control
			};

            ajaxControl.AjaxContext = newContext;            


			GlobalUpdateControl(control, newContext);


			Dictionary<string, object> callbackResult = new Dictionary<string, object>();
			callbackResult.Add("State", newContext.State);
			callbackResult.Add("Html", newContext.Html);

			return callbackResult;
		}


		protected override Dictionary<string, object> Update(CallbackStrategyArguments args)
		{
			// Create the context object to wrap the state and HTML of the IAjaxControl.
			AjaxControlContext newContext = new AjaxControlContext {
                State = args.state,
				Html = String.Empty,
				Control = LoadControl(args)
			};

			Control.AjaxContext = newContext;

            
            UpdateControl(newContext.Control, newContext);


            // Prepare the callback result.
			Dictionary<string, object> callbackResult = new Dictionary<string, object>();
			callbackResult.Add("State", newContext.State);
			callbackResult.Add("Html", newContext.Html);

			return callbackResult;
		}


		protected override Dictionary<string, object> GlobalInvoke(CallbackStrategyArguments args)
		{
			// Load the user control so that is may be initialized and its state restored from its AJAX context.
            Control control = LoadGlobalControl(args);

			if (!(control is IAjaxControl))
			{
				throw new System.Exception("The control being referenced by the 'Type' parameter of the GlobalInvoke method must implement the IAjaxControl interface.");
			}

            IAjaxControl ajaxControl = (IAjaxControl)control;


			// Create the context object to wrap the state and HTML of the IAjaxControl.
			AjaxControlContext newContext = new AjaxControlContext {
                State = args.state,
				Html = String.Empty,
				Control = control
			};


            ajaxControl.AjaxContext = newContext;


			// Invoke the desired method against the out-of-process Control.
            MethodInfo method = control.GetType().GetMethod(args.methodName, System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance, null, GetParameterTypes(args.parameters), null);
            object invokeResults = method.Invoke(control, args.parameters);


			Dictionary<string, object> callbackResult = new Dictionary<string, object>();

            if (args.returnHtml || args.autoUpdateHtml)
			{
				GlobalUpdateControl(control, newContext);

				if (invokeResults != null)
				{
					callbackResult.Add("Result", invokeResults);
					callbackResult.Add("State", newContext.State);
					callbackResult.Add("Html", newContext.Html);
				}
				else
				{
					callbackResult.Add("State", newContext.State);
					callbackResult.Add("Html", newContext.Html);
				}
			}
			else
			{
				if (invokeResults != null)
				{
					callbackResult.Add("Result", invokeResults);
					callbackResult.Add("State", newContext.State);
				}
				else
				{
					callbackResult.Add("State", newContext.State);
				}
			}

			return callbackResult;
		}


		protected override Dictionary<string, object> Invoke(CallbackStrategyArguments args)
		{
			// Create the context object to wrap the state and HTML of the AjaxUserControl.
			AjaxControlContext newContext = new AjaxControlContext {
                State = args.state,
				Html = String.Empty,
				Control = LoadControl(args)
			};
            
			Control.AjaxContext = newContext;


			Type controlType = Control.GetType();

			// Invoke the desired method against the Control.
            MethodInfo method = controlType.GetMethod(args.methodName, System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance, null, GetParameterTypes(args.parameters), null);
            object invokeResults = method.Invoke(Control, args.parameters);


			Dictionary<string, object> callbackResult = new Dictionary<string, object>();

			if (args.returnHtml || args.autoUpdateHtml)
			{
                UpdateControl(newContext.Control, newContext);

				if (invokeResults != null)
				{
					callbackResult.Add("Result", invokeResults);
					callbackResult.Add("State", newContext.State);
					callbackResult.Add("Html", newContext.Html);
				}
				else
				{
					callbackResult.Add("State", newContext.State);
					callbackResult.Add("Html", newContext.Html);
				}
			}
			else
			{
				if (invokeResults != null)
				{
					callbackResult.Add("Result", invokeResults);
					callbackResult.Add("State", newContext.State);
				}
				else
				{
					callbackResult.Add("State", newContext.State);
				}
			}

			return callbackResult;
		}



		protected virtual void UpdateControl(Control control, AjaxControlContext context)
		{
            (control as IAjaxControl).IsUpdating = true;


            // Invoke the PreRender event on the Control.
            PreRenderRecursiveInternal(context.Control);


            if (!control.Visible)
            {
                context.Html = String.Empty;
            }
            else
            {
                // Render the control manually.
                TextWriter html = new StringWriter();
                context.Control.RenderControl(new HtmlTextWriter(html));

                context.Html = html.ToString();
            }


            // Save the ControlState and ViewState of the Control being updated.
            if (Control.StateStrategy != null && context.Control.EnableViewState)
            {
                Control.StateStrategy.Save();
            }


            (control as IAjaxControl).IsUpdating = false;
		}


        // The purpose of this method is to call all remaining Page Lifecycle events 
        // (PreRender, Render, SaveControlState, SaveViewState, and Unload) in the correct order.
        protected virtual void GlobalUpdateControl(Control control, AjaxControlContext context)
        {
            IAjaxControl ajaxControl = control as IAjaxControl;

            ajaxControl.IsUpdating = true;


            if (!control.Visible)
            {
                context.Html = String.Empty;
            }
            else
            {
                // Render the control manually.
                TextWriter html = new StringWriter();
                context.Control.RenderControl(new HtmlTextWriter(html));

                context.Html = html.ToString();
            }


            // Save the ControlState and ViewState of the Control being updated.
            if (ajaxControl.StateStrategy != null && context.Control.EnableViewState)
            {
                ajaxControl.StateStrategy.Save();
            }


            UnloadRecursive(control, true);


            ajaxControl.IsUpdating = false;
        }


        protected virtual Type[] GetParameterTypes(object[] parameters)
        {
            Type[] parameterTypes = new Type[parameters.Length];

            for (int index = 0; index < parameters.Length; index++)
            {
                parameterTypes[index] = parameters[index].GetType();
            }

            return parameterTypes;
        }
        //------\\ Methods //-----------------------------------------------//
    }
}
